package cn.sanli.manage.aop;

import cn.sanli.manage.ex.ServiceException;
import cn.sanli.manage.web.ServiceCode;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 请求响应日志 AOP
 *
 * @author zk
 * @since 2023/06/26
 **/
@Aspect
@Component
@Slf4j
public class FilterInterceptor {

    /**
     * 访问限制白名单。
     */
    private static final List<String> MATCHES = Arrays.asList("/api/user/list/page","/api/user/selectUser","api/organization/listRegion",
             "/api/organization/listRegion","/api/organization/listDept","/api/system/menus/findMenus","/api/rewards/pagingQuery");


    /**
     * 缓存列表
     */
    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            // 最大缓存 1000 个
            .maximumSize(1000)
            // 设置写缓存后 10 秒钟过期
            .expireAfterWrite(1, TimeUnit.SECONDS)
            .build();

    /**
     * 执行拦截
     * 该Aop切面实现了日志信息的填充，控制了请求发送的时间【避免短时间提交多次相同请求造成异常】
     * @param point 拦截信息
     * @return Object
     * @throws Throwable 异常
     */
    @Around("execution(* cn.sanli.manage.controller.*.*(..))")
    public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
        // 获取请求路径
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 获取请求的方法路径
        String url = httpServletRequest.getRequestURI();
        // 获取请求参数
        Object[] args = point.getArgs();
        log.info("获取到的请求参数为："+Arrays.toString(args));
        //类型转换将 Object[] args =》 String
        String reqParam = "[" + StringUtils.join(args, ", ") + "]";
        log.info("reqParam的值【请求参数】为："+reqParam);
        String key = url + ":" + reqParam;
        log.info("key的值为："+key);
        /**
         *  contains该方法判断当前列表若包含某元素，返回结果为true, 若不包含该元素，返回结果为false。
         *  该条件判断主要针对于请求时间做限制
         */
        if (!(MATCHES.contains(url))) {
            if (!StringUtils.isEmpty(key)) {
                if (CACHES.getIfPresent(key) != null) {
                    log.error("request params is not same: {}", reqParam);
                    throw new ServiceException(ServiceCode.ERROR_BAD_REQUEST,  "短期内请勿重复相同请求");
                }
                // 如果是第一次请求,就将 key 当前对象压入缓存中
                CACHES.put(key, key);
            }
        }
        return point.proceed();
    }

    /**
     * key 的生成策略
     *
     * @param keyExpress 表达式
     * @param args       参数
     * @return 生成的key
     */
    private String getKey(String keyExpress, Object[] args) {
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
        }
        return keyExpress;
    }
}